﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using System.Diagnostics.Contracts;

using Seven.Web.IntelligentQuery.Model;
using Seven.Web.IntelligentQuery.TransformProviders;
using Seven.Tools.Extension;

namespace Seven.Web.IntelligentQuery.Extensions
{
    /// <summary>
    /// 对IQueryable的扩展方法，以支持利用QueryModel查询
    /// </summary>
    public static class QueryableExtensions
    {
        /// <summary>
        /// 使IQueryable支持QueryModel
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="table">IQueryable的查询对象</param>
        /// <param name="model">QueryModel对象</param>
        /// <param name="prefix">使用前缀区分查询条件</param>
        /// <returns></returns>
        public static IQueryable<TEntity> Where<TEntity>(this IQueryable<TEntity> table, QueryModel model, string prefix = "") where TEntity : class
        {
            Contract.Requires(table != null);
            if (model == null || model.Items.Count == 0) { return table; }
            return Where<TEntity>(table, model.Items, prefix);
        }

        private static IQueryable<TEntity> Where<TEntity>(IQueryable<TEntity> table, IEnumerable<ConditionItem> items, string prefix)
        {
            Contract.Requires(table != null);
            Contract.Requires(items != null);
            IEnumerable<ConditionItem> filterItems =
                string.IsNullOrWhiteSpace(prefix)
                    ? items.Where(c => string.IsNullOrEmpty(c.Prefix))
                    : items.Where(c => c.Prefix == prefix);
            if (filterItems.Count() == 0) { return table; }
            return new QueryableSearcher<TEntity>(table, filterItems).Search();
        }

        internal class QueryableSearcher<T>
        {
            public List<ITransformProvider> TransformProviders { get; set; }
            protected IEnumerable<ConditionItem> Items { get; set; }
            protected IQueryable<T> Table { get; set; }

            public QueryableSearcher()
            {
                TransformProviders = new List<ITransformProvider>
                {
                    //new LikeTransformProvider(),
                    //new DateBlockTransformProvider(),
                    //new InTransformProvider(),
                    //new UnixTimeTransformProvider(),
                    //new EnumTransformProvider()
                };
            }

            public QueryableSearcher(IQueryable<T> table, IEnumerable<ConditionItem> items)
                : this()
            {
                this.Table = table;
                this.Items = items;
            }

            public IQueryable<T> Search()
            {
                //构建 c=>Body中的c
                ParameterExpression param = Expression.Parameter(typeof(T), "c");
                //构建c=>Body中的Body
                var body = GetExpressoinBody(param, Items);
                if (body == null) { return this.Table; }
                //将二者拼为c=>Body
                var expression = Expression.Lambda<Func<T, bool>>(body, param);
                //传到Where中当做参数，类型为Expression<Func<T,bool>>
                return this.Table.Where(expression);
            }

            /// <summary>
            /// 根据条件集合获取每个条件对应属性和value的lambda表达式对象的集合表达式，用and或or链接各表达式，如：c => (c.RealName 运算符 "ABC" and c.Sex 运算符 "女") or (c.Phone 运算符 "12345")
            /// </summary>
            /// <param name="param"></param>
            /// <param name="items">条件集合</param>
            /// <returns></returns>
            private Expression GetExpressoinBody(ParameterExpression param, IEnumerable<ConditionItem> items)
            {
                var list = new List<Expression>();
                //不考虑Or的情况，即全为And组合
                var ex = GetGroupExpression(param, items, Expression.AndAlso);
                if (ex != null) { list.Add(ex); }
                //将这些Expression再以And相连
                return list.Count() == 0 ? null : list.Aggregate(Expression.AndAlso);
            }

            /// <summary>
            /// 根据条件集合获取每个条件对应属性和value的lambda表达式对象的集合表达式，用指定的func链接各表达式，如：c => (c.RealName 运算符 "ABC" 执行的func c.Sex 运算符 "女")
            /// </summary>
            /// <param name="param"></param>
            /// <param name="items">条件集合</param>
            /// <param name="func">指定的表达式连接方式</param>
            /// <returns></returns>
            private Expression GetGroupExpression(ParameterExpression param, IEnumerable<ConditionItem> items, Func<Expression, Expression, Expression> func)
            {
                //获取最小的判断表达式
                var list = items.Select(item => GetExpression(param, item)).Where(w => w != null);
                //再以逻辑运算符相连
                return list.Count() == 0 ? null : list.Aggregate(func);
            }

            /// <summary>
            /// 根据单个条件对象获取对应属性和value的lambda表达式对象，如：c => c.RealName 运算符 "ABC"
            /// </summary>
            /// <param name="param"></param>
            /// <param name="item">条件对象</param>
            /// <returns></returns>
            private Expression GetExpression(ParameterExpression param, ConditionItem item)
            {
                //获取属性的lambda表达式对象 如：c => c.RealName
                LambdaExpression exp = GetPropertyLambdaExpression(item, param);

                //常量表达式 如：RealName属性类型的"ABC"
                var constant = ChangeTypeToExpression(item, exp.Body.Type);
                //以判断符或方法连接
                return ExpressionDict[item.Method](exp.Body, constant);
            }

            /// <summary>
            /// 根据单个条件对象获取对应属性的lambda表达式对象，如：c => c.RealName
            /// </summary>
            /// <param name="item">条件对象</param>
            /// <param name="param"></param>
            /// <returns></returns>
            private LambdaExpression GetPropertyLambdaExpression(ConditionItem item, ParameterExpression param)
            {
                //获取每级属性如c.Users.Proiles.UserId
                var props = item.Field.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
                Expression propertyAccess = param;
                var typeOfProp = typeof(T);
                int i = 0;
                do
                {
                    PropertyInfo property = typeOfProp.GetProperty(props[i]);
                    if (property == null) { return null; }
                    typeOfProp = property.PropertyType;
                    propertyAccess = Expression.MakeMemberAccess(propertyAccess, property);
                    i++;
                } while (i < props.Length);

                return Expression.Lambda(propertyAccess, param);
            }

            /// <summary>
            /// 将查询条件中的Value值转换成目标类型并最终返回常量表达式
            /// </summary>
            /// <param name="item">查询条件对象</param>
            /// <param name="conversionType">目标类型</param>
            private static Expression ChangeTypeToExpression(ConditionItem item, Type conversionType)
            {
                if (item.Value == null) { return Expression.Constant(item.Value, conversionType); }

                //#region 数组

                //if (item.Method == QueryMethod.StdIn)
                //{
                //    var arr = (item.Value as Array);
                //    var expList = new List<Expression>();
                //    //确保可用
                //    if (arr != null)
                //        for (var i = 0; i < arr.Length; i++)
                //        {
                //            //构造数组的单元Constant
                //            var newValue = ChangeType(arr.GetValue(i), conversionType);
                //            expList.Add(Expression.Constant(newValue, conversionType));
                //        }
                //    //构造inType类型的数组表达式树，并为数组赋初值
                //    return Expression.NewArrayInit(conversionType, expList);
                //}

                //#endregion
                try
                {
                    var value = ChangeType(item.Value, conversionType);
                    return Expression.Constant(value, conversionType);
                }
                catch
                {
                    return Expression.Constant(conversionType.IsValueType ? Activator.CreateInstance(conversionType) : null, conversionType);
                }
            }

            #region ChangeType

            /// <summary>
            /// 将value转换成目标类型，若目标类型是可空类型，则将value转换成目标类型的非空类型
            /// </summary>
            /// <param name="value">要转换的对象</param>
            /// <param name="conversionType">目标类型</param>
            /// <returns></returns>
            private static object ChangeType(object value, Type conversionType)
            {
                if (value == null) { return null; }
                if (conversionType.IsEnum)
                {
                    return Enum.Parse(conversionType, value.ToString());
                }
                return Convert.ChangeType(value, conversionType.GetUnNullableType());
            }

            #endregion

            #region SearchMethod 操作方法

            private static readonly Dictionary<QueryMethod, Func<Expression, Expression, Expression>> ExpressionDict =
                new Dictionary<QueryMethod, Func<Expression, Expression, Expression>>
                {
                    { QueryMethod.Equal, (left, right) => { return Expression.Equal(left, right); } },
                    { QueryMethod.LessThan, (left, right) => {
                        if (!left.Type.IsValueType || left.Type.IsEnum) { return null; }
                        return Expression.LessThan(left, right); } },
                    { QueryMethod.GreaterThan, (left, right) => {
                        if (!left.Type.IsValueType || left.Type.IsEnum) { return null; }
                        return Expression.GreaterThan(left, right); } },
                    { QueryMethod.LessThanOrEqual, (left, right) => {
                        if (!left.Type.IsValueType || left.Type.IsEnum) { return null; }
                        return Expression.LessThanOrEqual(left, right); } },
                    { QueryMethod.GreaterThanOrEqual, (left, right) => {
                        if (!left.Type.IsValueType && left.Type != typeof(string)) { return null; }
                        if(left.Type.IsEnum)
                        { 
                            #region 枚举的比较特殊处理。没有实现，暂时不关注

                            //var methodInfo = typeof(Enum).GetMethod("CompareTo", new Type[] { typeof(Enum) });
                            //var tempLeft = Expression.Call(left, methodInfo, right);
                            //var tempRight = Expression.Constant(0, typeof(Int32));
                            //return Expression.GreaterThanOrEqual(tempLeft, tempRight);//比较Enum.CompareTo的返回结果和0，来实现>=的效果

                            return null;

                            #endregion
                        }
                        else if (left.Type == typeof(string))
                        {
                            #region 非数值类型的比较demo

                            var methodInfo = typeof(String).GetMethod("CompareTo", new Type[] { typeof(String) });
                            var tempLeft = Expression.Call(left, methodInfo, right);
                            var tempRight = Expression.Constant(0, typeof(Int32));
                            return Expression.GreaterThanOrEqual(tempLeft, tempRight);//比较String.CompareTo的返回结果和0，来实现>=的效果

                            #endregion
                        }
                        else
                        { return Expression.GreaterThanOrEqual(left, right); } }
                    },
                    { QueryMethod.Like, (left, right) => { return left.Type == typeof(string) ? (Expression.Call(left, typeof(string).GetMethod("Contains"), right)) : null; } },
                    { QueryMethod.NotEqual, (left, right) => { return Expression.NotEqual(left, right); } },
                    { QueryMethod.StartsWith, (left, right) => { return left.Type == typeof(string) ? (Expression.Call(left, typeof(string).GetMethod("StartsWith", new[] { typeof(string) }), right)) : null; } },
                    { QueryMethod.EndsWith, (left, right) => { return left.Type == typeof(string) ? (Expression.Call(left, typeof(string).GetMethod("EndsWith", new[] { typeof(string) }), right)) : null; } },
                    //{
                    //    QueryMethod.StdIn,
                    //    (left, right) =>
                    //        {
                    //            if (!right.Type.IsArray) return null;
                    //            //调用Enumerable.Contains扩展方法
                    //            MethodCallExpression resultExp =
                    //                Expression.Call(
                    //                    typeof (Enumerable),
                    //                    "Contains",
                    //                    new[] {left.Type},
                    //                    right,
                    //                    left);

                    //            return resultExp;
                    //        }
                    //    }
                };

            #endregion
        }
    }
}
